package com.xyy.saas.payment.adpater.fumin.client;

import com.alibaba.fastjson.JSONObject;
import com.xyy.saas.payment.adpater.fumin.config.*;
import com.xyy.saas.payment.adpater.fumin.encrypt.*;
import com.xyy.saas.payment.adpater.fumin.util.*;
import com.xyy.saas.payment.util.HttpClientUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.*;

public class FBankOpenApiClient {
    /**
     * 服务所使用的加密算法
     */
    private static final String ALGORITHM = "algorithm";
    /**
     * 加密算法服务类型
     **/
    private static final String ALGORITHM_TYPE = "rsaDetached";

    /**
     * 签名数据
     **/
    private static final String SIGN_DATA = "signData";

    /**
     * 配置
     */
    private Configuration config;

    public Configuration getConfig() {
        return config;
    }

    public void setConfig(Configuration config) {
        this.config = config;
    }

    public FBankOpenApiClient(Configuration config) {
        this.config = config;
    }

    /**
     * 发送请求
     * @param context
     * @return
     * @throws Exception
     */
    public String send(OpenParameters context) throws Exception {
        return sendBuild(context, context.getUserId(), context.getParam());
    }

    private String sendBuild(OpenParameters context, String userId, String params) throws Exception {
        // 对请求参数进行加密和签名
        String encryptedBody = buildBody(userId, params);

        // 校验请求流水号
        String clientSerialNo = context.getSerialNo();

        if(StringUtils.isBlank(clientSerialNo)) {
            clientSerialNo = SerialNoUtil.calculateSerialNo(params);
        }

        String encryptedResult = fetch(context, encryptedBody, clientSerialNo);
        JSONObject jsonObject = JSONObject.parseObject(encryptedResult);

        if(null == jsonObject || !jsonObject.containsKey(SIGN_DATA)) {
            return encryptedResult;
        }

        // 签名验证，失败抛出异常
        JSONObject json = VerifyAndDecryptUtil.verifySignature(encryptedResult, config.fbankSignPubKey());
        // 解密返回结果
        return VerifyAndDecryptUtil.decryptResult(json, config.privateKey());
    }

    private String buildBody(String userId, String json) throws Exception {
        // 生成AES 随机密钥
        String randomKey = AESUtils.randomKey();
        Encryptor encryptor = VerifyAndDecryptUtil.getEncryptor(config.encryptType());

        // 对AES密钥加密
        String encryptedRandomKey = encryptor.encrypt(randomKey, config.fbankEncPubKey());

        if(StringUtils.isBlank(json)) {
            JSONObject jsonObject = new JSONObject();
            json = JSONObject.toJSONString(jsonObject);
        }

        TreeMap<String, Object> tempMap = new TreeMap<>();
        tempMap.put("data", AESUtils.encrypt(json, randomKey));
        tempMap.put("randomKeyEncrypt", encryptedRandomKey);
        tempMap.put("timestamp", System.currentTimeMillis());
        tempMap.put("merchantNo", config.merchantNo());
        tempMap.put("channelNo", config.channelNo());
        tempMap.put("signType", config.signType());
        tempMap.put("encryptType", config.encryptType());
        tempMap.put("sdkVersion", config.sdkVersion());

        if(StringUtils.isNotBlank(config.appId()) && StringUtils.isNotBlank(config.siteId())) {
            tempMap.put("appId", config.appId());
            tempMap.put("siteId", config.siteId());
        }

        if(StringUtils.isNotBlank(userId)) {
            tempMap.put("userId", userId);
        }

        JSONObject jsonObject = new JSONObject(tempMap);
        String signStr = jsonObject.toJSONString();
        Encryptor signCryptor = VerifyAndDecryptUtil.getEncryptor(config.signType());
        // 对加密后的输入参数签名
        String signedStr = signCryptor.signature(signStr);
        jsonObject.put(SIGN_DATA, signedStr);
        return jsonObject.toJSONString();
    }

    private String fetch(OpenParameters context, String encryptedBody, String clientSerialNo) throws Exception {
        String path = pseudoModuleCode(context, config.remoteAddress());
        Map<String, String> header = setHttpHeaders(context, clientSerialNo);
        return getResponse(path, encryptedBody, header);
    }

    private static String pseudoModuleCode(OpenParameters context, String address) {
        if(!address.endsWith("/")) {
            address += "/";
        }

        if(ModuleCode.OPENAPI_FUNCTION.rawValue().equalsIgnoreCase(context.getModuleCode())) {
            address += "?function=" + context.getApiCode();
        }

        return address;
    }

    private static Map<String, String> setHttpHeaders(OpenParameters context, String clientSerialNo) {
        Map<String, String> header = new HashMap<>(16);
        header.put("api_name", context.getApiName());
        String apiCode = context.getApiCode();

        if(null != apiCode) {
            header.put("api_code", apiCode);
        }

        String moduleCode = context.getModuleCode();

        if(null != moduleCode) {
            header.put("module_code", moduleCode);
        }

        header.put("api_version", context.getApiVersion());
        header.put(ALGORITHM, ALGORITHM_TYPE);
        header.put("client_serial_no", clientSerialNo);
        header.put("Content-Type", "application/json");
        return header;
    }

    private static String getResponse(String url, String encryptedBody, Map<String, String> header) throws Exception {
        return HttpClientUtil.xyyHttpPostRequest(url, encryptedBody, "application/json", header);
    }
}
